from datetime import datetime
from re import compile
from math import pow
import libxml2

from error import *

__all__ = ["RdfDoc", "newrdfDoc", "findChild"]

class RdfDoc(libxml2.xmlDoc):
    """
    RDF Document, implementing specific behaviours by subclassing
    libxml2.xmlDoc

    Requires a configuration file with a [namespaces] section.
    """
    def __init__(self, config, *av, **kw):
    	libxml2.xmlDoc.__init__(self, *av, **kw)
    	self.config = config
    	self.root = libxml2.newNode("RDF")
    	rdfns = self.root.newNs("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf")
    	self.root.setNs(rdfns)
    	self.__namespaces__ = { "rdf" : rdfns }
    	self.addChild(self.root)

    def getNs(self, nsname):
    	if nsname not in self.__namespaces__:
    		nsurl = self.config.get("namespaces", nsname)
    		ns = self.root.newNs(nsurl, nsname)
    		self.__namespaces__[nsname] = ns
    	return self.__namespaces__[nsname]

    def newNsNode(self, nsname, name):
    	"""
    	An augmented version of libxml2.newNode, which makes sure that the 
    	appropriate namespace is declared, namespace pulled from the config
    	if necessary.

    	>>> doc = newRdfDoc("1.0", TestConfig)
    	>>> label = doc.newNsNode("rdfs", "label")
    	>>> label.setContent("Test Tag")
    	>>> unused = doc.root.addChild(label)
    	>>> print doc.serialize("utf-8", 1)
    	<?xml version="1.0" encoding="utf-8"?>
    	<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
    	  <rdfs:label>Test Tag</rdfs:label>
    	</rdf:RDF>
    	<BLANKLINE>
    	"""
    	node = libxml2.newNode(name)
    	node.setNs(self.getNs(nsname))
    	return node

    ###
    ### Parse Map functions. Each of these functions return a function that
    ### parses a bit of data out of a string and returns an XML node appropriately
    ### formatted. They normally take a name and a format or regular expression
    ###
    def strNode(self, ns, name, rexp = '^(.*)$'):
    	"""
    	Produces RDF Literal reference.
    	Strips enclosing double-quotes and space.

    	>>> doc = newRdfDoc("1.0", TestConfig)
    	>>> f = doc.strNode("envisat", "testString")
    	>>> f(doc.root, '"Hello World"')
    	>>> print doc.serialize("utf-8", 1)
    	<?xml version="1.0" encoding="utf-8"?>
    	<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:envisat="http://www.example.com/schemas/envisat.rdf#">
    	  <envisat:testString>Hello World</envisat:testString>
    	</rdf:RDF>
    	<BLANKLINE>
    	"""
    	rexpc = compile(rexp)
    	def _f(parent, s):
    		s = s.strip('"').strip()
    		m = rexpc.match(s)
    		if not m:
    			raise FormatError("%s: %s" % (name, s))
    		n = self.newNsNode(ns, name)
    		content = m.groups()[0]
    		content = content.replace("&", "&amp;")
    		n.setContent(content)
    		parent.addChild(n)
    	return _f

    def resNode(self, ns, name, rexp = '^(.*)$', prefix=''):
    	"""
    	Produces an RDF resource reference.
    	Strips enclosing double-quotes.
    
    	>>> doc = newRdfDoc("1.0", TestConfig)
    	>>> f = doc.resNode("envisat", "testResource")
    	>>> f(doc.root, '"http://www.example.net/"')
    	>>> print doc.serialize("utf-8", 1)
    	<?xml version="1.0" encoding="utf-8"?>
    	<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:envisat="http://www.example.com/schemas/envisat.rdf#">
    	  <envisat:testResource rdf:resource="http://www.example.net/"/>
    	</rdf:RDF>
    	<BLANKLINE>
    	"""
    	rexpc = compile(rexp)
    	def _f(parent, s):
    		s = s.strip('"').strip()
    		m = rexpc.match(s)
    		if not m:
    			raise FormatError("%s: %s" % (name, s))
    		n = self.newNsNode(ns, name)
    		resource = prefix + m.groups()[0]
    		resource = resource.replace("&", "%%%s" % (ord("&")))
    		n.setNsProp(self.getNs("rdf"), "resource", resource)
    		parent.addChild(n)
    	return _f

    def dateNode(self, ns, name, fmt = '%d-%b-%Y %H:%M:%S'):
    	"""
    	Produces a literal with a standardly formatted (ISO) date.
    	Strips enclosing double-quotes.
    
    	>>> doc = newRdfDoc("1.0", TestConfig)
    	>>> f = doc.dateNode("envisat", "testDate")
    	>>> f(doc.root, '"30-SEP-2004 22:53:35.678230"')
    	>>> print doc.serialize("utf-8", 1)
    	<?xml version="1.0" encoding="utf-8"?>
    	<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:envisat="http://www.example.com/schemas/envisat.rdf#">
    	  <envisat:testDate>2004-09-30T22:53:35.678230</envisat:testDate>
    	</rdf:RDF>
    	<BLANKLINE>
    	"""
    	def _f(parent, s):
    		s = s.strip('"')
    		try:
    			dt, ms = s.split(".")
    		except ValueError:
    			dt, ms = s, "%06d" % 0
    		dt = datetime.strptime(dt, fmt)
    		n = self.newNsNode(ns, name)
    		n.setContent("%s.%s" % (dt.isoformat(), ms))
    		parent.addChild(n)
    	return _f
    
def newRdfDoc(version, config):
    """
    >>> doc = newRdfDoc("1.1", TestConfig)
    >>> print doc
    <?xml version="1.1"?>
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
    <BLANKLINE>
    """
    ret = libxml2.newDoc(version)
    if ret is None:
    	raise treeError('newDoc() failed')
    return RdfDoc(config, _obj=ret._o)

def findChild(node, name):
    """
    Helper function for libxml2.

    Find the child of node with the given name.

    >>> foo = libxml2.newNode("foo")
    >>> bar = libxml2.newNode("bar")
    >>> baz = libxml2.newNode("baz")
    >>> unused = foo.addChild(bar), foo.addChild(baz)
    >>> print findChild(foo, "baz").serialize()
    <baz/>
    >>> print findChild(foo, "nonexistent")
    None
    """
    for child in node.children:
    	if child.name == name:
    		return child

if __name__ == '__main__':
    from tempfile import mktemp
    from os import unlink
    from sys import argv
    import doctest

    from config import RdfConfig
    from optparse import Values

    testconfig = """
[namespaces]
rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs=http://www.w3.org/2000/01/rdf-schema#
owl=http://www.w3.org/2002/07/owl#
envisat=http://www.example.com/schemas/envisat.rdf#
"""
    options = Values({ 'config' : mktemp() })
    fp = open(options.config, "w+")
    fp.write(testconfig)
    fp.close()
    TestConfig = RdfConfig(options)
    print "where are my doctests?"
    doctest.testmod()

    unlink(options.config)
